import tkinter

import numpy as np
import pandas as pd

from module.static_module.parent.view import AdditionWindowView
from core.constant import *
from tools.view import *
from tools.framework import *
from conf import conf


class BacktestingVisualizationView(AdditionWindowView):
    def __init__(self, super_model):
        super().__init__(super_model)
        self.value_close_visual = None
        self.withdrawal_visual = None
        self.cash_visual = None
        self.i = 0
        self.win_draw_num = int((conf.AdditionModule.visualization_x_px.value - conf.AdditionModule.axis_y_px.value) // conf.AdditionModule.k_px.value)
        self.ori_close = None
        self.value_2_close_rate = None
        self.ori_draw_data = None

    def create_layout_init(self):
        # 绘制标签、内容组件
        # 配置参数
        equity_file_ls = self.super_model.equity_file_ls  # 净值文件
        trade_file_ls = self.super_model.trade_file_ls  # 交易文件
        order_file_ls = self.super_model.order_file_ls  # 订单文件

        label_config = [
            [LabelMember.EquityFile, WidgetCategory.Entry, equity_file_ls, [0]],
            [LabelMember.TradeFile, WidgetCategory.Entry, trade_file_ls, [0]],
            [LabelMember.OrderFile, WidgetCategory.Entry, order_file_ls, [0]]
        ]
        button_config = [
            [ButtonMember.Ok, WidgetCategory.Button, None]
        ]
        self.auto_layout = AutoLayout(self.frame, label_config, button_config)
        self.auto_layout.button_command(self)

    def draw_dynamic_graph(self):
        self.ori_draw_data = self.super_model.draw_data.copy()
        # 生成tk窗口
        root = tk.Toplevel()
        root.title("DDQuant 回测可视化")
        # 计算canvas布局，并生成canvas
        is_axis_y = True
        total_x_px = conf.AdditionModule.visualization_x_px.value + conf.AdditionModule.axis_y_px.value if is_axis_y \
            else conf.AdditionModule.visualization_x_px.value

        value_close_y_px = conf.AdditionModule.visualization_x_px.value // 1.8 // 5 * 3
        withdrawal_y_px = conf.AdditionModule.visualization_x_px.value // 1.8 // 5 * 1
        cash_y_px = conf.AdditionModule.visualization_x_px.value // 1.8 // 5 * 1

        root.geometry(f"{total_x_px+6}x{int(value_close_y_px+withdrawal_y_px+cash_y_px+80)}")

        value_close_frame = tk.Frame(root, borderwidth=1, relief='solid')
        value_close_canvas = tk.Canvas(value_close_frame, width=total_x_px, height=value_close_y_px+conf.AdditionModule.axis_x_px.value)
        value_close_canvas.pack(fill='both', expand=True)
        value_close_frame.grid(row=0, column=0, sticky="nsew")

        withdrawal_frame = tk.Frame(root, borderwidth=1, relief='solid')
        withdrawal_canvas = tk.Canvas(withdrawal_frame, width=total_x_px, height=withdrawal_y_px)
        withdrawal_canvas.pack(fill='both', expand=True)
        withdrawal_frame.grid(row=1, column=0, sticky="nsew")

        cash_frame = tk.Frame(root, borderwidth=1, relief='solid')
        cash_canvas = tk.Canvas(cash_frame, width=total_x_px, height=cash_y_px)
        cash_canvas.pack(fill='both', expand=True)
        cash_frame.grid(row=2, column=0, sticky="nsew")

        # 初始化相应sub visual，并将canvas分配
        self.value_close_visual = SubVisual(self)
        self.value_close_visual.gen_layout(value_close_y_px, True, True)
        self.value_close_visual.set_canvas(value_close_canvas, ["red", "gray"], 1)

        self.withdrawal_visual = SubVisual(self)
        self.withdrawal_visual.gen_layout(withdrawal_y_px, False, True)
        self.withdrawal_visual.set_canvas(withdrawal_canvas, ["black"], 1)

        self.cash_visual = SubVisual(self)
        self.cash_visual.gen_layout(cash_y_px, False, True)
        self.cash_visual.set_canvas(cash_canvas, ["orange"], 1)
        # 提取标兵值
        #  / equity_data_sampled["value"][0] * equity_data_sampled["close"][0]
        self.ori_close = self.ori_draw_data["close"][0]
        # value 2 close rate
        self.value_2_close_rate = self.ori_close / conf.AdditionModule.ori_value.value
        self.ori_draw_data["value"] = self.ori_draw_data["value"] * self.value_2_close_rate
        self.update()

    def update(self):
        # 分拆数据为不同帧对应数据，设置数据并渲染
        self.i += 1
        # 生成px数据
        if self.i < self.win_draw_num:
            value_close_draw_data = self.ori_draw_data.iloc[0:self.i][["value", "close"]]
            self.value_close_visual.gen_px_data(value_close_draw_data, True)
            withdrawal__draw_data = self.ori_draw_data.iloc[0:self.i]["withdrawal"]
            self.withdrawal_visual.gen_px_data(withdrawal__draw_data)
            cash_draw_data = self.ori_draw_data.iloc[0:self.i]["cash"]
            self.cash_visual.gen_px_data(cash_draw_data)
        elif self.win_draw_num <= self.i < self.super_model.draw_num:
            value_close_draw_data = self.ori_draw_data.iloc[self.i-self.win_draw_num:self.i][["value", "close"]]
            self.value_close_visual.gen_px_data(value_close_draw_data, True)
            withdrawal__draw_data = self.ori_draw_data.iloc[self.i-self.win_draw_num:self.i]["withdrawal"]
            self.withdrawal_visual.gen_px_data(withdrawal__draw_data)
            cash_draw_data = self.ori_draw_data.iloc[self.i-self.win_draw_num:self.i]["cash"]
            self.cash_visual.gen_px_data(cash_draw_data)
        elif self.super_model.draw_num <= self.i <= self.super_model.draw_num + conf.AdditionModule.backtrack_time.value:
            # i超过数据最大条数，在n个time interval内回溯至第一组数据
            # 筛选的数据应再次取样恰好铺满屏幕
            exceed_time = self.i - self.super_model.draw_num + 1
            exceed_data_len = self.super_model.draw_num - self.win_draw_num
            group_num = exceed_data_len // conf.AdditionModule.backtrack_time.value

            value_close_draw_data = self.ori_draw_data.iloc[-(self.win_draw_num+group_num*exceed_time):][["value", "close"]]
            indices = np.linspace(0, len(value_close_draw_data) - 1, self.win_draw_num).astype(int)
            value_close_draw_data = value_close_draw_data.iloc[indices]
            self.value_close_visual.gen_px_data(value_close_draw_data, True)
            withdrawal__draw_data = self.ori_draw_data.iloc[-(self.win_draw_num+group_num*exceed_time):]["withdrawal"]
            withdrawal__draw_data = withdrawal__draw_data.iloc[indices]
            self.withdrawal_visual.gen_px_data(withdrawal__draw_data)
            cash_draw_data = self.ori_draw_data.iloc[-(self.win_draw_num+group_num*exceed_time):]["cash"]
            cash_draw_data = cash_draw_data.iloc[indices]
            self.cash_visual.gen_px_data(cash_draw_data)
        else:
            return
        # 清空canvas内容
        self.value_close_visual.del_canvas()
        self.withdrawal_visual.del_canvas()
        self.cash_visual.del_canvas()
        # 绘制坐标轴
        self.value_close_visual.draw_axis()
        self.withdrawal_visual.draw_axis()
        self.cash_visual.draw_axis()
        # 绘制y轴垂直线
        self.value_close_visual.draw_y_line()
        self.withdrawal_visual.draw_y_line()
        self.cash_visual.draw_y_line()
        # 绘制元素
        self.value_close_visual.draw_ele()
        self.withdrawal_visual.draw_ele()
        self.cash_visual.draw_ele()
        # 绘制图例
        self.value_close_visual.draw_legend()
        self.withdrawal_visual.draw_legend()
        self.cash_visual.draw_legend()
        if self.i <= 1:
            self.root.after(9000, self.update)
        else:
            self.root.after(conf.AdditionModule.time_interval.value, self.update)
        pass


class SubVisual:
    def __init__(self, super_view):
        self.super_view = super_view
        self.draw_data = None
        self.draw_px_data = None
        self.axis_x = False
        self.axis_y = False
        self.legend = True
        self.visualization_x = conf.AdditionModule.visualization_x_px.value
        self.visualization_y = None
        self.ele_size = []
        self.win_size = []
        self.max_min_ls = []
        self.canvas = None
        self.color_ls = None
        self.lines_size = None
        self.y_axis_px_ls = None

    def gen_layout(self, visualization_y_px, draw_axis_x: bool, draw_axis_y: bool):
        self.visualization_y = visualization_y_px
        self.axis_x = draw_axis_x
        self.axis_y = draw_axis_y
        if self.axis_y:
            self.win_size.append(self.visualization_x + conf.AdditionModule.axis_y_px.value)
        else:
            self.win_size.append(self.visualization_x)
        if self.axis_x:
            self.win_size.append(self.visualization_y + conf.AdditionModule.axis_x_px.value)
        else:
            self.win_size.append(self.visualization_y)

    def set_canvas(self, canvas, color_ls, lines_size):
        self.canvas = canvas
        self.color_ls = color_ls
        self.lines_size = lines_size

    def gen_px_data(self, draw_data, draw_sig=False):
        self.draw_data = draw_data
        if isinstance(draw_data, pd.Series):
            self.draw_data = pd.DataFrame(draw_data)
        if draw_sig:
            # 生成买卖点对应坐标
            if self.super_view.super_model.order_data is not None:
                # 先筛选出指定数量的信号值
                if len(self.super_view.super_model.order_data) > conf.AdditionModule.order_num.value:
                    indices = np.linspace(0, len(self.super_view.super_model.order_data) - 1, conf.AdditionModule.order_num.value).astype(int)
                    self.super_view.super_model.order_data = self.super_view.super_model.order_data.iloc[indices]
                # 遍历order_data将order与其最接近的样本合并，新增buy或sell字段
                # 根据equity_data的index范围截取order_data
                begin_index = self.draw_data.index[0]
                end_index = self.draw_data.index[-1]
                intercept_order_data = self.super_view.super_model.order_data.loc[begin_index:end_index]

                for index, row in intercept_order_data.iterrows():
                    # 找到index等于或最小于的一个equity的index，并将价格合并至equity
                    filtered_equity = self.draw_data[self.draw_data.index <= index]
                    if filtered_equity.empty:
                        pass
                    else:
                        index_equity = filtered_equity.index[-1]
                        price = row["price"]
                        direction = "buy" if row["direction"] == "Direction.LONG" else "sell"
                        self.draw_data.loc[index_equity, direction] = price
        self.max_min_ls = [self.draw_data.max().max()*1.015, self.draw_data.min().min()*0.985]
        distance = self.max_min_ls[0] - self.max_min_ls[1]
        temp_df = self.visualization_y - (self.draw_data - self.max_min_ls[1]) / distance * self.visualization_y
        self.draw_px_data = temp_df

    def draw_ele(self):
        # 绘制line
        k_cont_width = conf.AdditionModule.k_px.value
        k_x = 0
        line_points = []
        # 剔除可能的buy和sell字段
        all_field = self.draw_px_data.columns.tolist()
        real_field = []
        for field in all_field:
            if field not in ["buy", "sell"]:
                real_field.append(field)

        real_draw_px_data = self.draw_px_data[real_field]
        for j, (index, o) in enumerate(real_draw_px_data.iterrows()):
            x = k_x + k_cont_width
            y = tuple(o.tolist())
            line_points.append((x, y))
            k_x += k_cont_width

            if len(line_points) >= 2:
                x1 = line_points[-2][0]
                x2 = line_points[-1][0]
                for i, y_f in enumerate(y):
                    y1 = line_points[-2][1][i]
                    y2 = line_points[-1][1][i]
                    if real_field[i] == "value":
                        self.canvas.create_line(x1, y1, x2, y2, width=self.lines_size, fill=self.color_ls[i])
                    else:
                        self.canvas.create_line(x1, y1, x2, y2, width=self.lines_size, fill=self.color_ls[i])

                # 如果buy或sell在draw_px_data中
                if "buy" in all_field and not np.isnan(self.draw_px_data.loc[index, "buy"]):
                    # 绘制buy信号
                    buy_x = x2
                    buy_y = self.draw_px_data.loc[index, "buy"]-3
                    # 绘制形状
                    radius = 3
                    # self.canvas.create_oval(buy_x - radius, buy_y - radius, buy_x + radius, buy_y + radius, fill="red")
                    triangle_points = [(buy_x-radius, buy_y+radius), (buy_x+radius, buy_y+radius), (buy_x, buy_y-radius)]
                    self.canvas.create_polygon(triangle_points, fill="red", outline="#CC3333")

                if "sell" in all_field and not np.isnan(self.draw_px_data.loc[index, "sell"]):
                    # 绘制sell信号
                    sell_x = x2
                    sell_y = self.draw_px_data.loc[index, "sell"]+3
                    # 绘制形状
                    radius = 3
                    # self.canvas.create_oval(sell_x - radius, sell_y - radius, sell_x + radius, sell_y + radius, fill="green")
                    triangle_points = [(sell_x-radius, sell_y-radius), (sell_x+radius, sell_y-radius), (sell_x, sell_y+radius)]
                    self.canvas.create_polygon(triangle_points, fill="green", outline="#80BB80")

            if j == len(self.draw_px_data)-1:
                for k, name in enumerate(all_field):
                    if name == "value":
                        x_o = line_points[-1][0]
                        y_o = line_points[-1][1][k]
                        text_value = round(self.draw_data.loc[index, name] / self.super_view.value_2_close_rate, 2)
                        text_value_str = str(text_value)+f"({round((text_value-conf.AdditionModule.ori_value.value)/conf.AdditionModule.ori_value.value*100,2)}%)"
                        if text_value >= conf.AdditionModule.ori_value.value:
                            fill_color = "red"
                        else:
                            fill_color = "green"
                        self.canvas.create_text(x_o + int(conf.AdditionModule.axis_y_px.value / 2), y_o,
                                                text=text_value_str, font=conf.AdditionModule.font.value, fill=fill_color)
                        # 绘制小圆点
                        radius = 2
                        self.canvas.create_oval(x_o - radius, y_o - radius, x_o + radius, y_o + radius, fill=fill_color)
                    elif name == "close":
                        x_o = line_points[-1][0]
                        y_o = line_points[-1][1][k]
                        text_value = round(self.draw_data.loc[index, name], 2)
                        text_value_str = str(text_value)
                        fill_color = "gray"

                        self.canvas.create_text(x_o + int(conf.AdditionModule.axis_y_px.value / 2), y_o,
                                                text=text_value_str, font=conf.AdditionModule.font.value, fill=fill_color)
                        # 绘制小圆点
                        radius = 2
                        self.canvas.create_oval(x_o - radius, y_o - radius, x_o + radius, y_o + radius, fill=fill_color)
                    elif name == "withdrawal":
                        x_o = line_points[-1][0]
                        y_o = line_points[-1][1][k]
                        text_value = round(self.draw_data.loc[index, name], 2)
                        text_value_str = str(text_value)
                        fill_color = "black"

                        self.canvas.create_text(x_o + int(conf.AdditionModule.axis_y_px.value / 2), y_o,
                                                text=text_value_str, font=conf.AdditionModule.font.value, fill=fill_color)
                        # 绘制小圆点
                        radius = 2
                        self.canvas.create_oval(x_o - radius, y_o - radius, x_o + radius, y_o + radius, fill=fill_color)
                    elif name == "cash":
                        x_o = line_points[-1][0]
                        y_o = line_points[-1][1][k]
                        text_value = round(self.draw_data.loc[index, name], 2)
                        text_value_str = str(text_value)
                        fill_color = "orange"

                        self.canvas.create_text(x_o + int(conf.AdditionModule.axis_y_px.value / 2), y_o,
                                                text=text_value_str, font=conf.AdditionModule.font.value, fill=fill_color)
                        # 绘制小圆点
                        radius = 2
                        self.canvas.create_oval(x_o - radius, y_o - radius, x_o + radius, y_o + radius, fill=fill_color)
        pass

    def draw_y_line(self):
        # 补充绘制y轴线
        x1 = 0
        x2 = conf.AdditionModule.visualization_x_px.value
        for y_axis_px in self.y_axis_px_ls:
            y1 = y2 = y_axis_px[1]
            self.canvas.create_line(x1, y1, x2, y2, width=1, fill="#D3D3D3")

    def draw_axis(self):
        if self.axis_x:
            k_px = conf.AdditionModule.k_px.value
            k_num_interval = conf.AdditionModule.axis_x_interval.value // k_px
            # 获取绘制点坐标及其数值
            draw_px_ls = []
            draw_text_ls = []
            draw_num = int(len(self.draw_data) // k_num_interval)+1  # 获取最多绘制数量
            for i in range(draw_num):
                if i == draw_num - 1:
                    continue  # 最后一个不绘制，为了美观
                # 绘制文本的坐标
                text_px_x = (i + 1) * k_num_interval * k_px - k_px / 2
                text_px_y = conf.AdditionModule.axis_x_px.value / 2
                draw_px_ls.append([text_px_x, text_px_y])
                # 绘制文本的数值，此时记录datetime类型的时间数据
                text_dt = self.draw_data.iloc[int(i*k_num_interval)].name  # 定位到该位置对应的k线数据并获取相应series的name
                draw_text_ls.append(text_dt)

            # 处理draw_text_ls，判断k线的级别
            interval_ts_ls = []
            index_ls = self.draw_data.index.tolist()
            for i, o in enumerate(index_ls):
                if i == 0:
                    continue
                interval_ts_ls.append(index_ls[i].timestamp() - index_ls[i - 1].timestamp())

            interval_ts_dc = {}
            for i, o in enumerate(interval_ts_ls):
                if o in interval_ts_dc.keys():
                    interval_ts_dc[o] += 1
                else:
                    interval_ts_dc[o] = 1

            key_ls = list(interval_ts_dc.keys())
            val_ls = list(interval_ts_dc.values())
            if val_ls:
                arg_max = np.argmax(val_ls)
                interval_ts = key_ls[arg_max]
                if interval_ts < 60:  # 如果小于一分钟
                    # 展示 月-日 时：分
                    for i, o in enumerate(draw_text_ls):
                        draw_text_ls[i] = o.strftime("%d %H:%M:%S")

                elif interval_ts < 60 * 60 * 24:  # 如果小于一天
                    # 展示 月-日 时：分
                    for i, o in enumerate(draw_text_ls):
                        draw_text_ls[i] = o.strftime("%m-%d %H:%M")
                else:
                    # 展示 年-月-日
                    for i, o in enumerate(draw_text_ls):
                        draw_text_ls[i] = o.strftime("%Y-%m-%d")
                # 画到图标上
                for i, o in enumerate(draw_text_ls):
                    text_x = draw_px_ls[i][0]
                    text_y = draw_px_ls[i][1]

                    self.canvas.create_text(text_x, text_y+self.visualization_y, text=o, font=conf.AdditionModule.font.value, fill="black")
                    # 绘制|表示刻度
                    self.canvas.create_text(text_x, 0+self.visualization_y, text='.', font=conf.AdditionModule.font.value, fill="black")
            else:
                pass
            pass
        if self.axis_y:

            y_axis_text_ls = []
            self.y_axis_px_ls = []

            # 最大绘制y坐标文本数量
            y_text_draw_num = int(self.visualization_y // conf.AdditionModule.axis_y_interval.value)
            # 确定0值
            if self.max_min_ls[0] > 0 > self.max_min_ls[1]:
                y_axis_text_ls.append("0")
                px_0 = value2px(0, self.max_min_ls, self.visualization_y)
                self.y_axis_px_ls.append([conf.AdditionModule.axis_y_px.value // 2, px_0])
                px_move = px_0
                while True:
                    px_move -= conf.AdditionModule.axis_y_interval.value
                    if px_move < 0:
                        break
                    y_axis_text_ls.append(str(px2value(px_move, self.max_min_ls, self.visualization_y)))
                    self.y_axis_px_ls.append([conf.AdditionModule.axis_y_px.value // 2, px_move])
                px_move = px_0
                while True:
                    px_move += conf.AdditionModule.axis_y_interval.value
                    if px_move > self.visualization_y:
                        break
                    y_axis_text_ls.append(str(px2value(px_move, self.max_min_ls, self.visualization_y)))
                    self.y_axis_px_ls.append([conf.AdditionModule.axis_y_px.value // 2, px_move])
            else:
                for i in range(y_text_draw_num):
                    px_move = (i + 1) * conf.AdditionModule.axis_y_interval.value
                    value = px2value(px_move, self.max_min_ls, self.visualization_y)
                    if np.isnan(value):
                        y_axis_text_ls.append('')
                    else:
                        y_axis_text_ls.append(str(value))
                    self.y_axis_px_ls.append([conf.AdditionModule.axis_y_px.value // 2, px_move])

            for i, text_o in enumerate(y_axis_text_ls):
                text = text_o
                text_x = self.y_axis_px_ls[i][0]
                text_y = self.y_axis_px_ls[i][1]
                self.canvas.create_text(text_x + self.visualization_x, text_y, text=text, font=conf.AdditionModule.font.value, fill="black")
                # 绘制一根-表示刻度线
                self.canvas.create_text(0 + self.visualization_x, text_y, text='-', font=conf.AdditionModule.font.value, fill="black")
            pass

        pass

    def draw_legend(self):
        occupy_multi = 1.8
        occupy_px = conf.AdditionModule.font.value[1] * occupy_multi
        # 剔除可能的buy和sell字段
        all_field = self.draw_px_data.columns.tolist()
        real_field = []
        for field in all_field:
            if field not in ["buy", "sell"]:
                real_field.append(field)
        if self.legend:
            # 生成框的坐标
            high = occupy_px * len(real_field)
            # text_cont_ax = [self.win_size[0]-100, 20, self.win_size[0]-20, 20+high]
            text_cont_ax = [20, 20, occupy_px * 6, 20 + high]
            # 创建文本框
            # text_cont_ax[0], text_cont_ax[1], text_cont_ax[2], text_cont_ax[3]
            self.canvas.create_rectangle(text_cont_ax[0], text_cont_ax[1], text_cont_ax[2], text_cont_ax[3],
                                         width=1,
                                         fill="white")
            # 框内写图例
            # ele_type类型： bar：绘制k线 volume：绘制交易量柱 line：绘制线条 point：绘制点 tank：绘制箱体
            # line_byk：绘制线条 point_byk：绘制点  // 区别在于byk以k线最大最小值为准，否则以对应指标最大最小值为准
            for i, o in enumerate(real_field):
                shape = '--'
                color = self.color_ls[i]
                self.canvas.create_text(text_cont_ax[0] + text_cont_ax[2] / 2 - 10,
                                        text_cont_ax[1] + occupy_px / 2 + i * occupy_px,
                                        text=o + ':' + shape, font=conf.AdditionModule.font.value, fill=color)
        pass

    def del_canvas(self):
        # 清空canvas
        self.canvas.delete("all")
